Here is the usual "release notes" describing the changes from the previous Mops version, so that those already using it don't have to wade through all the documentation (or have to read right through the huge manual!!!).
This is mainly a consolidation release, mainly aimed at making Mops even easier for beginners. The manual has had a revision, and a few new features have been added, mainly aimed at consistency. For beginners (and for others as well), a very useful principle is the Principle of Minimum Astonishment™, and I've been trying to apply this as much as possible. There are a couple of really new features as well, most noteably temporary objects and a PowerPC assembler. But I didn't go overboard with new features—I don't want the dreaded "code bloat" to strike Mops!
2.5 also fixes some minor bugs that have been reported for 2.4.1.
Temporary objects
Temporary objects are rather like local variables. They could also be called "local objects" but the word "local" is being used for enough things already, so we're calling them "temporary". It's the same thing, though. They are normal objects in all respects, except that they only exist within one definition (or local section), and have no storage allocated otherwise. The syntax is as in this example:
: SomeWord
temp{ int anInt
var aVar
string aString }
123 put: anInt
" hello" put: aString
...
;
You can also use the syntax
temp { ... }
with "temp" and "{" as separate words, if you prefer. I tend to use both, depending on how many temporary objects I'm declaring, and how I want to format the declaration.
As you can see, within the definition you can use the temporary objects in exactly the same way as normal objects. They are actually allocated in a frame on the return stack. However you can use >R etc freely in the definition, since I keep a separate frame pointer. Of course, the temporary objects get a classInit: message automatically when the definition is entered and their space is allocated. They also get a release: message automatically when the definition exits (either at the semicolon or via EXIT). Thus if you use a temporary string as in the above example, you don't have to worry about sending it release: to get rid of its heap storage at the end of the definition.
As with local variables, if you call a definition recursively, you will get a fresh copy of any temporary objects.
The syntax for temporary objects within a local section is exactly as you would expect:
LOCAL localName { parm1 parm2 \ loc1 loc2 }
temp{ int1
var1 }
The local variables here are entirely optional. A local section can have either local variables, or temporary objects, or both. (Not much point in having neither!!)
ivar enhancements
Something which has occasionally caused confusion is the difference between ivars and "normal" objects. In most ways, ivars were the same as normal objects, but there were a couple of differences which were somewhat unexpected, let's say.
* You couldn't send a late-bound message to an ivar unless its class was declared as "general", since the default was that ivars didn't get an object header (which contains the class pointer).
* If you just named an ivar (without a message) you would get an "undefined word" error, whereas you could name a normal object and cause its address to get pushed on the stack.
There were historical reasons for these differences, but these reasons weren't particulary compelling. Accordingly we've removed the differences, so that you can now treat ivars as normal objects. This is the "principle of minimum astonishment" which I mentioned above.
This means that ivars now get an object header by default. The word GENERAL isn't needed any more. An ivar is an object, period.
There is one situation, however, in which we don't want an ivar to have an object header, and that is if the ivar is being used to define a field of a structure which is passed to the Mac Toolbox. This was actually the main reason why ivars didn't have object headers before, as the normal default. For example, if a field is defined as a C int or short, or a Pascal integer, we need to use a Mops 2-byte int without any other bytes being stored.
However, as this is only one use for ivars, it's better for us to provide a mechanism for dealing with this special case, and have ivars otherwise be normal objects.
So here's the way to specify a block of ivars which don't have object headers. We'll call this block a "record". The syntax is as in this example:
:class someClass super{ object }
record{ int i1
int i2
var v1
}
int i3
var v2
...
;class
You can also use the syntax:
record { ... }
Note the similarity to the syntax for temporary objects—this is deliberate. This is the "principle of minimum astonishment" again.
ivars declared outside the { ... } are normal ivars with object headers.
If you look in the Mops source files for the various Toolbox-related classes, you'll see plenty of examples of how to use this syntax in mapping ivars to Toolbox data.
You can also give the record a name, thus:
record recName { ... }
If you give it a name, you can send the addr: method to the name to get the address of the beginning of the record.
Sending messages to superclasses
With multiple inheritance, there's sometimes a need to send a message to a particular superclass. Normally if you send a message to SUPER the superclasses are searched in the order they appeared in the class declaration, from left to right. However if the message you're sending is implemented in a class to the left of the one you actually want it to go to, there's a problem. So far we've had a rather inelegant way of getting around this, using a value supers_to_skip, but now we have a better way:
msg: super> someSuper
which is pretty self-explanatory. Thanks to Greg Stevenson for suggesting this syntax.
Object initialization improvements
There were some anomalies in our object initialization, which occasionally surfaced and bit people.
When an object was created, all its ivars were sent classinit:, and so was the new object. However the object's superclass wasn't separately sent classinit:. This was OK with single inheritance, since if you didn't override classinit:, it would go to the superclass anyway, and if you did override it, it was your responsibility to call classinit: super if this was appropriate.
But with multiple inheritance there were problems. If classinit: was sent to super, either automatically because you didn't override classinit:, or because you called it explicitly, the message only went to the leftmost superclass! This could cause strange problems if you really expected all the superclasses to get classinit:, which isn't really an unreasonable thing to expect. So, applying the "principle of minimum astonishment" again, we now send classinit: to all superclasses, no matter what. Even if you override classinit:, by the time your classinit: executes, all the superclasses will have already received their classinit: messages, so you can assume the appropriate initial values are in all the ivars, etc, without any problems. You now don't need to send classinit: super explicitly any more.
PowerPC assembler and disassembler
Mops can't generate native PPC code yet—maybe we'll get there for the next release—but in the meantime we do have a PPC assembler and disassembler, thanks to Xan Gregg. Xan has written this assembler for the PPC version of MacForth, and he's kindly allowing me to distribute it with Mops in exchange for a program I've written to parse Apple's new Universal Headers.
I've adapted the assembler to the Mops environment, so it's a module, pasmMod (with the source file pasmMod.txt). You write a PPC code definition thus:
:ppc_code someName
<ppc instructions>
;ppc_code
This will create a normal header for someName in the dictionary, then align the DP to a 4-byte boundary (as required for PPC code), then compile the code. Note that if you tick someName, the resulting "cfa" mightn't be on a 4-byte boundary since our cfa's are only 2-byte aligned for the 680x0. So the "cfa" should be rounded up to the next 4-byte boundary to get the address of the first PPC instruction in the definition. Possibly when we have a full native PPC version of Mops we might do something a bit more sensible here.
There's no manual for the assembler or disassembler yet—you're on your own! But the comments in the source files are fairly extensive, so if you're the type of person who might want to write PPC assembly, you'll probably be able to figure out what to do! —especially as the file "test pasm" in the System Source folder has a definition containing all PPC instructions. Note: it's a Forth-style postfix assembler.
The PPC assembler-related files are:
pasmMod.txt in "Module Source" the assembler.
disasm in "Module Source" the disassembler
(loaded by pasmMod.txt).
test pasm in "System Source" a big test definition with all the PPC
instructions.
A new kind of comment
There's a new way of defining a comment, intended to encoursge large blocks of commentary. The syntax is (* this is a comment *) and the comment can continue over multiple lines—as many as you like. This style of comment can also be nested—thus:
(* this is a comment (* and so is this *) *)
This style of comment is also suitable for commenting out blocks of code while you're experimenting, especially with the nesting feature.
The Mops window text limit
Several people (including me) have found that when the Mops window was getting near to its 32k limit, the alert that was displayed was a major pest. So now when we get near the limit, some text from the start is simply deleted, without any alert or message of any kind. So now you can dump huge amounts of memory or whatever, and you'll always have the last 32k of text in the Mops window.
Thoughts on the future
This section always appears in my release notes, and is for thoughts on the future development of Mops. The big commercial outfits don't "speculate on unnanounced products", but not only do I do it, I welcome feedback!
Mops, being full-featured but free, is becoming a good language for beginners who don't want to fork out $$$ for a commercial system, when they're still tentative about what their programming future might be. Therefore with this 2.5 release I've been concentrating on improving Mops for beginners.
But the main thing from now on will be to continue work on a native PowerPC version. This will probably take well into next year (1995). I don't really want to do another release before then.
Every now and then I chew over some wild ideas like persistent objects and garbage collection, but the native PowerPC version is going to come before everything else now.
I've also been thinking about evolving Mops in the direction of becoming a proper application framework. This would involve a more general command-dispatching mechanism than we have now, and a Document class which will link windows to disk files. These ideas are still in the fluid stage, so I won't do anything until I have a better idea of the right way to go. In 2.5 we have an "undoable action" class (see the file "Undo" in the folder "System source"), but this is still really experimental.
Many frameworks are huge and difficult to learn. But with multiple inheritance it ought to be possible for Mops to incorporate framework concepts without code bloat. CodeWarrior from MetroWerks shows how this can be done, and I'll be looking carefully at what they've done to see what might be applicable to Mops.
The PowerPC also opens up some other interesting questions—noteably, the eventual change to a 64-bit system. The first 64-bit PowerPC chips will probably arrive in 1995. I would like to be able to adapt Mops to this new environment, especially as the Forth standard now avoids any particular word-length dependencies.
Under a 64-bit implementation an address will become 64 bits. The ANSI standard specifies that a stack cell must be able to hold an address, so a stack cell will have to become 64 bits as well. @ and ! refer to stack cell sized data, so they will become 64 bit operations. After some discussion on comp.lang.forth about this, the best proposal for operations of other lengths are 8@, 16@, 32@ etc. It's far too confusing to refer to "halfwords" or "longwords" or whatever. Let's call a spade a shovel and be done with it. C@ will remain the same, and thus be an alias for 8@.
For objects, ints and vars should probably remain as they are, with something new for 64-bit operands. Do people often store addresses in vars? Is this a problem? We have an Addr class, so I hope vars haven't been used for addresses. If they have, var would have to become 64 bits long and we'd need something new for 32 bits. But I'd prefer to leave var as 32 bits and have something new for 64. That's what I'll do unless I get a lot of objections.
In any case, 32-bit machines will be with us for a very long time yet, so 64-bit compilation will have to be an option. It would be nice if code can be compiled and run successfully under either option without changes, so this is what I'll aim at.
E-mail etc.
If you need to contact me, the quickest way is via e-mail:
Internet: mikeh@zeta.org.au
CompuServe: 100033,3164
My mail address is
Michael Hore
54 Frederick St
Sydenham NSW 2044,
AUSTRALIA.
I also read the newsgroups comp.lang.forth and comp.lang.forth.mac, so you can post there and I'll see it, especially if you put "Mops" somewhere in the subject line.
There is now a Mops section in the Forth Forum on CompuServe (GO FORTH)—it's section 4. I check there every couple of days if possible.
There was also a Mops topic on GEnie (Forth RoundTable, category 7, topic 40), but I guess this is now defunct, since I don't have access to GEnie any more, and probably won't in the foreseeable future. So if you need to contact me, do it in one of the other ways.